function [bci,bciDat]=bciSSVEPFeedback(bci)

% [bci,bciDat]=bciSSVEPFeedback(bci)
% performs a feedback run 

global MEMepoch;
global MEMdata;
global GLOBALtrainSet;
global GLOBALbci;

if ~isfield(bci,'goodChan'),
    error('Field for good channels missing. bciTrain may help.');
end

doDetectArtifacts = ~true; % set this parameter false, if artifacts can be ignored

stimprm.mode=1; % feedback mode
stimprm=bciSSVEPControl(bci,'init',stimprm); % stimulus initialisation

% initialize some things
score=zeros(1,length(bci.eventsToClassify)); % number of target hits
performedTrials=zeros(1,length(bci.eventsToClassify)); % number of trials
count     = 0;
trialCount=0;
predCount=zeros(1,length(bci.eventsToClassify));
blocksize  = round(bci.srate *0.1); % 1s when choosing samprate
eventTimeOut = 3600;
doSendTrig = false;
% initialize mat data
% estimate size
nEpochs = round((bci.testRunDur+10)/bci.param.winsrate * bci.srate);
nRTsamp = round(bci.RT*bci.srate);
nRetrainSamp = floor(bci.paradigm.fbClassifierTrialLength*bci.param.winsrate);
isRetrainTrial = false(1,nEpochs);
epochLabel = zeros(1,nEpochs);
epochPrediction = zeros(1,nEpochs);
epochTrialID = zeros(1,nEpochs);
epochOnset = zeros(1,nEpochs);
effectorEvents = zeros(2,nEpochs);
badChanMap = false(length(bci.preprocChan),nEpochs);
% get the events
bci=bciGenerateEvents(bci,'test'); % test flag no impact here
estimatedEvents=length(bci.events);

pause(bci.preRunDur);
[hdr,bci]=bciReadRecorderHeader(bci,'retry',1);
endsample = hdr.nSamples;
prevSample = endsample;
startSample = endsample; % this variable is required in bciTriggerProcessingInclude 

% start
startTime=GetSecs;
testEvents = zeros(2,estimatedEvents);
eventTime = zeros(1,estimatedEvents);
trigRec = zeros(1,estimatedEvents);
trigPos = zeros(1,estimatedEvents);
trigRecTime = zeros(1,estimatedEvents);
nextCursorRefresh = startTime;
prevTestSample = 0;
datCount=0;
epochCount = 0;
eventCount=0;
trigCount=0;
effectorCount=0;
eventChanged = true;
doBufferWarning = true;
newEvent = false;
lastEvent=0;
lastEventSamp=inf;
lastTrialReset=inf;
hitTarget=true;
stimprm.hitSuccess=false;
curEvent = 0;
lastHitTime=startTime;
runFinished = false;
while ~runFinished, % sec run duration
    % determine number of samples available in buffer
    [hdr,bci]=bciReadRecorderHeader(bci,'retry',1);
    newsamples = (hdr.nSamples*hdr.nTrials-prevSample);

    if newsamples>=blocksize

        % determine the samples to process
        begsample  = prevSample+1;
        endsample  = prevSample+newsamples ;

        % remember up to where the data was read
        prevSample  = endsample;
        count       = count + 1;

        % read data segment from buffer
        MEMdata(:,datCount+1:datCount+newsamples) = single(bciReadRecorderData(bci, hdr, begsample, endsample));
        curEventNo = eventCount; % this variable is required in bciTriggerProcessingInclude 
        bciTriggerProcessingInclude; % trigger processing
        datCount=datCount+newsamples;
        if eventChanged,
            % set next expected classifier window onset to event onset
            prevTestSample = lastEventSamp + nRTsamp - bci.init.nOffset+1;
            eventChanged=false;
        end

        while datCount > prevTestSample+bci.init.nEpochLength,
            % test next block
            curEpochIdx = prevTestSample+1:prevTestSample+bci.init.nEpochLength;
            if bci.init.doNoiseCancellation,
                curEpochData = bciNoiseCancellation(...
                    double(MEMdata(bci.preprocChan,curEpochIdx)),...
                    double(MEMdata(bci.referenceChan,curEpochIdx)));
            else
                curEpochData = MEMdata(bci.preprocChan,curEpochIdx);
            end
            [ep badChan] = bciGetEpoch(bci,curEpochData);
            if (~doDetectArtifacts || isempty(intersect(bci.goodChan,find(badChan))))&&...% there was no artifact detected
                    prevTestSample+bci.init.nOffset-lastEventSamp > nRTsamp && ... % Reaction time reached
                    lastEvent==8 && ~stimprm.trialClassified ;
                epochCount = epochCount + 1;
                badChanMap(:,epochCount) = badChan;
                epT=bciTransformData(ep,bci,1);
                testEvent = bci.param.testFunc(bci,epT(:,bci.featureMask));
                MEMepoch(:,:,epochCount) = ep;
                epochLabel(epochCount) = stimprm.lastFlickerEvent;
                epochPrediction(epochCount)=testEvent;
                epochOnset(epochCount) = prevTestSample+1;
                epochTrialID(epochCount) = trialCount;                                                
                isRetrainTrial(epochCount)=epochCount<(lastTrialReset+nRetrainSamp);
            else
                testEvent=[];
            end
            timeStamp = GetSecs;
            if doBufferWarning && abs(timeStamp-startTime-(prevTestSample+bci.init.nEpochLength)/bci.srate)>1,
                fprintf('Warning: buffer timing difference > %2.2f s\n',timeStamp-startTime-(prevTestSample+bci.init.nEpochLength)/bci.srate);
            end
            prevTestSample = prevTestSample+bci.init.nWinShift;
            predCount(testEvent)=predCount(testEvent)+1;
        end
    else%if newsamples==0, %if no samples available pause to prevent "failed to create buffer" error
        pause(0.1);
    end % if new samples available
    
    timeStamp = GetSecs;
    % select
    if timeStamp>nextCursorRefresh && ~hitTarget,
        stimprm.curEvent=curEvent;
        stimprm.predictedInTrial=epochPrediction(lastTrialReset:epochCount);
        stimprm = bciSSVEPControl(bci,'select',stimprm);
        if stimprm.mostPred>0,
            effectorCount=effectorCount+1;
            effectorEvents(:,effectorCount)=[timeStamp-startTime;stimprm.mostPred];
            % send trigger of effector
            if bci.param.sendStateTrigger,
                trigCount=trigCount+1;
                bciSendTrigger(bci,10*stimprm.mostPred+stimprm.lastFlickerEvent);
            elseif bci.param.doSendTrigger && stimprm.trialClassified,
                bciSendTrigger(bci,9); % send feedback trigger
            end
        end
        if stimprm.trialClassified && stimprm.mostPred>0,
            fprintf('Class corr: %i Tm: %3.2f\n',stimprm.correctTarget,timeStamp-startTime);
            % auditory feedback??
        end
        if timeStamp-lastHitTime>=eventTimeOut,
            % for flicker events extend the eventTimeOut if trigger used
            % for timing
            if ~(bci.param.doReceiveTrigger && lastEvent==8)|| timeStamp-lastHitTime>=eventTimeOut*2,
                newEvent = true;
                % ignore eventTimeout if gripper is working
                if bci.paradigm.id==13 && stimprm.gripperState>0,% add: && lastEvent==8, to start flicker while robot reset
                    newEvent=false;
                end
            end
        end
        if stimprm.hitTarget || (timeStamp-lastHitTime>= bci.paradigm.fbTrialTimeOut &&  ...
                                    ~stimprm.trialClassified && lastEvent==8 && ...
                                    ~(bci.paradigm.id==13 && stimprm.gripperState>0)),
            lastHitTime=timeStamp;
            performedTrials(stimprm.lastFlickerEvent)=performedTrials(stimprm.lastFlickerEvent)+1;
            hitTarget = true;
            fprintf('Hit: %i Tm: %3.2f\n',stimprm.hitTarget,timeStamp-startTime);
        end
        nextCursorRefresh = timeStamp + 1/bci.cursorRate;
    end
    if hitTarget || newEvent,
        timeStamp=GetSecs;
        if (timeStamp-lastHitTime>bci.paradigm.fbBreakDur&&hitTarget) || ...
                newEvent || trialCount == 0,            
            if stimprm.hitSuccess,
                score(stimprm.lastFlickerEvent)=score(stimprm.lastFlickerEvent)+1;
            end
            % get new event
            trialCount=trialCount+1;
            if trialCount==length(bci.events),runFinished=true;end
            lastTrialReset = epochCount+1;
            curEvent = bci.events(trialCount);
            eventTimeOut = bci.eventDur(trialCount);
            stimprm.curEvent=curEvent;
            stimprm=bciSSVEPControl(bci,'newevent',stimprm);
            hitTarget=false;
            newEvent = false;
            lastHitTime=timeStamp;
            doSendTrig=true;
            doRetrainClassifier = bci.param.retrainClassifier&&bci.paradigm.fbClassifierTrialLength>0;
            doBufferWarning = ~doRetrainClassifier;
        end
        if doSendTrig,
            trigCount=trigCount+1;
            % setting events
            eventCount=eventCount+1;
            eventTime(eventCount) = timeStamp-startTime;
            testEvents(:,eventCount)=[eventTime(eventCount);curEvent];
            fprintf('%i. Evt: %i Tm: %3.2f\n',eventCount,curEvent,eventTime(eventCount));
            % send trigger
            if bci.param.doSendTrigger,
                bciSendTrigger(bci,curEvent);
            end
            if ~bci.param.doReceiveTrigger,
                lastEventSamp=datCount;
                lastEvent=curEvent;
                eventChanged = true;
            end
        end
        doSendTrig = false;
        if doRetrainClassifier && any(curEvent==bci.eventsToClassify),
            labelIdx = find(isRetrainTrial(1:epochCount));
            if ~isempty(labelIdx),
                [bci]=bciRetrainClassifier(bci,labelIdx,epochLabel(labelIdx)');
                fprintf('Data set retrained.\n');
                doBufferWarning = false;
                % refresh data pointer to prevent timing asynchonism
                % not sure if necessary; solve when timing problems occur
                doRetrainClassifier = false;
            end
        end
    end
end % while true

bciSSVEPControl(bci,'finish',stimprm);

if bci.param.doSendTrigger,
    bciSendTrigger(bci,4);
end

bciDat.nRawData = datCount;
bciDat.nEpochs = epochCount;

bciDat.trainLabel=epochLabel(1:epochCount);
bciDat.isRetrainTrial=isRetrainTrial(1:epochCount);
bciDat.prediction=epochPrediction(1:epochCount);
bciDat.trialID=epochTrialID(1:epochCount);
bciDat.testOnset=epochOnset(1:epochCount);
bciDat.testEvents=testEvents(:,1:eventCount);
bciDat.effectorEvents=effectorEvents(:,1:effectorCount);
bciDat.trigRecTime = trigRecTime(1:eventCount);
bciDat.trigRec = trigRec(1:eventCount);
bciDat.trigPos = trigPos(1:eventCount);
bciDat.badChanMap=badChanMap(:,1:epochCount);
bciDat.score=score;
bciDat.performedTrials = performedTrials;
formatStr = repmat(' @%2.3f: %i / %i',1,length(bci.eventsToClassify));
resultMat = [bci.paradigm.flickerFreqs(bci.paradigm.flickerAlignment);...
            score;performedTrials];
fprintf([' Correct selected:\n' formatStr '\n'], resultMat(:));

% save the updated Train data in memory; bciTrain will overwrite it!
if bci.param.retrainClassifier,
    [GLOBALbci Train]=bciRetrainClassifier(bci,labelIdx,epochLabel(labelIdx)',false);
    GLOBALtrainSet = Train;
end